#include "pch.h"

#include "SoundManager.h"
#include <algorithm>

namespace
{  
   struct WaveHeaderType
   {
      char chunkId[4];
      unsigned long chunkSize;
      char format[4];
      char subChunkId[4];
      unsigned long subChunkSize;
      unsigned short audioFormat;
      unsigned short numChannels;
      unsigned long sampleRate;
      unsigned long bytesPerSecond;
      unsigned short blockAlign;
      unsigned short bitsPerSample;
      char dataChunkId[4];
      unsigned long dataSize;
   };
}

dxroids::SoundManager::SoundManager()
   : muted_(false)
   , initialized_(false)
{   
}

HRESULT
dxroids::SoundManager::Initialize(HWND _window_handle)
{
   HRESULT result = DirectSoundCreate(nullptr, &sound_device_, nullptr);
   if (FAILED(result)) 
   {
      return result;
   }

   result = sound_device_->SetCooperativeLevel(_window_handle, DSSCL_PRIORITY);
   if (FAILED(result)) 
   {      
      return result;
   }

   // Setup the primary buffer description.
   DSBUFFERDESC buffer_description;   
   buffer_description.dwSize = sizeof(DSBUFFERDESC);
   buffer_description.dwFlags = DSBCAPS_PRIMARYBUFFER | DSBCAPS_CTRLVOLUME;
   buffer_description.dwBufferBytes = 0;
   buffer_description.dwReserved = 0;
   buffer_description.lpwfxFormat = NULL;
   buffer_description.guid3DAlgorithm = GUID_NULL;
   
   for (int i = 0; i < DXROIDS_PLAYER_SHIP_MAX_SHOTS; ++i) 
   {      
      DXROIDS_COM_PTR<IDirectSoundBuffer> buffer;      
      if (LoadWaveFromResource(MAKEINTRESOURCE(IDR_WAVE_FIRE), buffer)) 
      {
         sound_effect_fire_.push_back(buffer);
      }

      buffer.Release();
      if (LoadWaveFromResource(MAKEINTRESOURCE(IDR_WAVE_BANG_LARGE), buffer))
      {
         sound_effect_band_large_.push_back(buffer);
      }

      buffer.Release();
      if (LoadWaveFromResource(MAKEINTRESOURCE(IDR_WAVE_BANG_MEDIUM), buffer))
      {
         sound_effect_band_medium_.push_back(buffer);
      }

      buffer.Release();
      if (LoadWaveFromResource(MAKEINTRESOURCE(IDR_WAVE_BANG_SMALL), buffer))
      {
         sound_effect_band_small_.push_back(buffer);
      }
   }
      
   LoadWaveFromResource(MAKEINTRESOURCE(IDR_WAVE_BEAT1), SoundEffect::BeatOne);
   LoadWaveFromResource(MAKEINTRESOURCE(IDR_WAVE_BEAT2), SoundEffect::BeatTwo);
   LoadWaveFromResource(MAKEINTRESOURCE(IDR_WAVE_EXTRA_SHIP), SoundEffect::ExtraShip);
   LoadWaveFromResource(MAKEINTRESOURCE(IDR_WAVE_SAUCER_BIG), SoundEffect::SaucerBig);
   LoadWaveFromResource(MAKEINTRESOURCE(IDR_WAVE_SAUCER_SMALL), SoundEffect::SaucerSmall);
   LoadWaveFromResource(MAKEINTRESOURCE(IDR_WAVE_THRUST), SoundEffect::Thrust);

   initialized_ = SUCCEEDED(result);
   return result;
}

void dxroids::SoundManager::LoadWaveFromResource(LPTSTR _resource_id, SoundEffect _effect)
{
   DXROIDS_COM_PTR<IDirectSoundBuffer> temp_effect;
   LoadWaveFromResource(_resource_id, temp_effect);
   if (temp_effect != nullptr)
   {
      sound_effects_.insert(sound_buffer_map_type::value_type(_effect, temp_effect));
   }
}

DXROIDS_COM_PTR<IDirectSoundBuffer> dxroids::SoundManager::FindSoundEffect(SoundEffect _effect) const
{
   DXROIDS_COM_PTR<IDirectSoundBuffer> sound_buffer = nullptr;

   if (_effect == SoundEffect::Fire || _effect == SoundEffect::BangLarge ||
      _effect == SoundEffect::BangMedium || _effect == SoundEffect::BangSmall)
   {   
      sound_buffer_list_type const * effect_list = nullptr;

      switch (_effect)
      {
      case SoundEffect::Fire: 
         effect_list = &sound_effect_fire_;
         break;
      case SoundEffect::BangLarge:
         effect_list = &sound_effect_band_large_;
         break;
      case SoundEffect::BangMedium:
         effect_list = &sound_effect_band_medium_;
         break;
      case SoundEffect::BangSmall:
         effect_list = &sound_effect_band_small_;
         break;
      }

      if (effect_list != nullptr)
      {
         for (auto begin = effect_list->begin(), end = effect_list->end(); 
            begin != end && sound_buffer == nullptr; ++begin) 
         {
            DWORD status;
            if (sound_buffer == nullptr && SUCCEEDED((*begin)->GetStatus(&status))) 
            {
               if (!(status & DSBSTATUS_PLAYING))             
                  sound_buffer = *begin;            
            }
         }
      }
   }
   else
   {
      auto itr = sound_effects_.find(_effect);
      if (itr != sound_effects_.end())
      {
         sound_buffer = itr->second;
      }
   }
   
   return sound_buffer;
}

void dxroids::SoundManager::Stop(SoundEffect _effect) const
{
   DxRoidsAssert(_effect != SoundEffect::Fire);

   DXROIDS_COM_PTR<IDirectSoundBuffer> sound_buffer = FindSoundEffect(_effect);
   if (sound_buffer != nullptr)
   {
      sound_buffer->Stop();
   }
}


void dxroids::SoundManager::Play(SoundEffect _effect, bool _loop) const
{
   if (muted_ || !initialized_)
   {
      return;
   }

   DXROIDS_COM_PTR<IDirectSoundBuffer> sound_buffer = FindSoundEffect(_effect);

   if (sound_buffer != nullptr)
   {
      DWORD flags = 0;
      if (_loop)
      {
         flags = DSBPLAY_LOOPING;
      }      
      sound_buffer->Play(0, 0, flags);
   }
}

bool
dxroids::SoundManager::LoadWaveFromResource(LPTSTR _resource_id, DXROIDS_COM_PTR<IDirectSoundBuffer>& _sound_buffer)
{   
   HRSRC resource = FindResource(nullptr, _resource_id, L"WAVE");     
   if (resource == nullptr)
      return false;   

   HGLOBAL loaded = LoadResource(HINST_THISCOMPONENT, resource);
   if (loaded == nullptr)
      return false;   

   BYTE * buffer = reinterpret_cast<BYTE *>(LockResource(loaded));

   DWORD const buffer_length = SizeofResource(HINST_THISCOMPONENT, resource);
   bool const did_load = LoadWaveFile(buffer, buffer_length, _sound_buffer);
   UnlockResource(loaded);

   return did_load;
}

bool dxroids::SoundManager::LoadWaveFile(BYTE * _buffer, DWORD _buffer_length, DXROIDS_COM_PTR<IDirectSoundBuffer>& _sound_buffer)
{
   //int error;
   // FILE* filePtr;
   //size_t count;
   WaveHeaderType waveFileHeader;
   WAVEFORMATEX waveFormat;
   DSBUFFERDESC bufferDesc;
   HRESULT result;

   DXROIDS_COM_PTR<IDirectSoundBuffer> tempBuffer;
   unsigned char* waveData;
   unsigned char* bufferPtr;
   unsigned long bufferSize;


   // Open the wave file in binary.
   //error = fopen_s(&filePtr, filename, "rb");
   //if(error != 0)
   //{
      //return false;
   //}

   // Read in the wave file header.
   //count = fread(&waveFileHeader, sizeof(waveFileHeader), 1, filePtr);
   //if(count != 1)
   //{
      //return false;
   //}
   CopyMemory(&waveFileHeader, _buffer, sizeof(waveFileHeader));


   // Check that the chunk ID is the RIFF format.
   if (strncmp(waveFileHeader.chunkId, "RIFF", 4) != 0)
   {
      return false;
   }

   //if ((waveFileHeader.chunkId[0] != 'R') || (waveFileHeader.chunkId[1] != 'I') || 
      //(waveFileHeader.chunkId[2] != 'F') || (waveFileHeader.chunkId[3] != 'F'))
   //{
      //return false;
   //}

   // Check that the file format is the WAVE format.
   //if ((waveFileHeader.format[0] != 'W') || (waveFileHeader.format[1] != 'A') ||
      //(waveFileHeader.format[2] != 'V') || (waveFileHeader.format[3] != 'E'))
   //{
      //return false;
   //}

   if (strncmp(waveFileHeader.format, "WAVE", 4) != 0)
   {
      return false;
   }

   // Check that the sub chunk ID is the fmt format.
   if ((waveFileHeader.subChunkId[0] != 'f') || (waveFileHeader.subChunkId[1] != 'm') ||
      (waveFileHeader.subChunkId[2] != 't') || (waveFileHeader.subChunkId[3] != ' '))
   {
      return false;
   }   

   if (strncmp(waveFileHeader.subChunkId, "fmt ", 4) != 0)
   {
      return false;
   }

   // Check that the audio format is WAVE_FORMAT_PCM.
   if (waveFileHeader.audioFormat != WAVE_FORMAT_PCM)
   {
      return false;
   }

   // Check that the wave file was recorded in stereo format.
   //if(waveFileHeader.numChannels != 2)
   //{
      //return false;
   //}

   // Check that the wave file was recorded at a sample rate of 44.1 KHz.
   //if(waveFileHeader.sampleRate != 44100)
   //{
      //return false;
   //}

   // Ensure that the wave file was recordead in 16 bit formt.
   //if(waveFileHeader.bitsPerSample != 16)
   //{
      //return false;
   //}

   // Check for the data chunk header.
   //if ((waveFileHeader.dataChunkId[0] != 'd') || (waveFileHeader.dataChunkId[1] != 'a') ||
      //(waveFileHeader.dataChunkId[2] != 't') || (waveFileHeader.dataChunkId[3] != 'a'))
   //{
      //return false;
   //}
  
   bool has_fact = false;
   if (strncmp(waveFileHeader.dataChunkId, "data", 4) != 0)
   {     
      if (strncmp(waveFileHeader.dataChunkId, "fact", 4) == 0)
      {
         has_fact = true;             
      }
      else
      {
         return false;
      }
   }

   // Move to the beginning of the wave data which starts at the end of the data chunk header.
   // fseek(filePtr, sizeof(WaveHeaderType), SEEK_SET);
   _buffer = _buffer + sizeof(WaveHeaderType);

   // this is really ugly, but for some reason I cannot find anything to load .wav files   
   // I hacked it up to skip past the 'fact' chunk
   if (has_fact)
   {
      char const * p = (char const *) _buffer;
      while (strncmp(p, "data", 4) != 0)
      {
         p++;
      }
      if (strncmp(p, "data", 4) == 0)
      {
         p += 4;
      }
      int const * size = (int const *) (p);
      waveFileHeader.dataSize = *size;
      if (p)
      {
         _buffer = (BYTE *)p + 4;
      }
   }


   // Set the wave format of secondary buffer that this wave file will be loaded onto.
   waveFormat.wFormatTag = WAVE_FORMAT_PCM;
   //waveFormat.nSamplesPerSec = 44100;
   //waveFormat.wBitsPerSample = 16;
   //waveFormat.nChannels = 2;
   waveFormat.nSamplesPerSec = 11025;
   waveFormat.wBitsPerSample = waveFileHeader.bitsPerSample; //8;
   waveFormat.nChannels = 1;
   waveFormat.nBlockAlign = (waveFormat.wBitsPerSample / 8) * waveFormat.nChannels;
   waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign;
   waveFormat.cbSize = 0;

   // Set the buffer description of the secondary sound buffer that the wave file will be loaded onto.
   bufferDesc.dwSize = sizeof(DSBUFFERDESC);
   bufferDesc.dwFlags = DSBCAPS_CTRLVOLUME;
   bufferDesc.dwBufferBytes = waveFileHeader.dataSize;
   bufferDesc.dwReserved = 0;
   bufferDesc.lpwfxFormat = &waveFormat;
   bufferDesc.guid3DAlgorithm = GUID_NULL;

   // Create a temporary sound buffer with the specific buffer settings.
   result = sound_device_->CreateSoundBuffer(&bufferDesc, &tempBuffer, nullptr);
   if (FAILED(result))
   {
      return false;
   }

   // Test the buffer format against the direct sound 8 interface and create the secondary buffer.   
   result = tempBuffer->QueryInterface(IID_IDirectSoundBuffer, (LPVOID *) &_sound_buffer);
   if (FAILED(result))
   {
      return false;
   }     

   // Create a temporary buffer to hold the wave file data.
   waveData = new unsigned char[waveFileHeader.dataSize]; 

   // Read in the wave file data into the newly created buffer.
   //count = fread(waveData, 1, waveFileHeader.dataSize, filePtr);
   CopyMemory(waveData, _buffer, waveFileHeader.dataSize);
   //if(count != waveFileHeader.dataSize)
   //{
     // return false;
   //}

   // Close the file once done reading.
   //error = fclose(filePtr);
   //if(error != 0)
   //{
      //return false;
   //}   
   // Lock the secondary buffer to write wave data into it.
   result = _sound_buffer->Lock(0, waveFileHeader.dataSize, (void**)&bufferPtr, (DWORD*)&bufferSize, nullptr, 0, 0);
   if (FAILED(result))
   {
      return false;
   }

   // Copy the wave data into the buffer.
   memcpy(bufferPtr, waveData, waveFileHeader.dataSize);

   // Unlock the secondary buffer after the data has been written to it.
   result = _sound_buffer->Unlock((void*)bufferPtr, bufferSize, nullptr, 0);
   if (FAILED(result))
   {
      return false;
   }

   // Release the wave data since it was copied into the secondary buffer.
   delete [] waveData;
   waveData = 0;

   return true;
}
